﻿/*
BF4Rcon.NET, provides a .NET implementation of BF4's RCON interface.
Copyright (C) 2013 Tim Marro

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TimScripts.BF4Rcon.Frostbite.Resources;

namespace TimScripts.BF4Rcon.Frostbite
{
    public abstract class StreamRconConnection : IRconConnection<Packet>
    {
        private Stream DataStream;
        private IPacketConverter<Packet> Converter;
        private const short BufferSize = 4096;

        protected void SetStream(Stream stream)
        {
            if (DataStream != null)
                throw new InvalidOperationException(PortableErrorStrings.InvalidOperation_StreamAlreadySet);

            DataStream = stream;
            Converter = new PacketConverter(RequestDataCallback, RequestDataCallbackAsync);
        }

        public void SendPacket(Packet packet)
        {
            if (DataStream == null)
                throw new StreamNotSetException();

            if (!IsConnected)
                throw new DisconnectionException();

            byte[] data = Converter.GetPacketBytes(packet);

            try
            {
                DataStream.Write(data, 0, data.Length);
                DataStream.Flush();
            }
            catch
            {
                Disconnect();
                throw;
            }

        }

        public async Task SendPacketAsync(Packet packet)
        {
            if (DataStream == null)
                throw new StreamNotSetException();

            if (!IsConnected)
                throw new DisconnectionException();

            byte[] data = Converter.GetPacketBytes(packet);

            try
            {
                await DataStream.WriteAsync(data, 0, data.Length).ConfigureAwait(false);
            }
            catch
            {
                Disconnect();
                throw;
            }

        }

        public Packet ReceivePacket()
        {
            if (DataStream == null)
                throw new StreamNotSetException();

            if (!IsConnected)
                throw new DisconnectionException();

            Packet result;

            try
            {
                result = Converter.GetPacket();
            }
            catch
            {
                Disconnect();
                throw;
            }

            return result;
        }

        public async Task<Packet> ReceivePacketAsync()
        {
            if (DataStream == null)
                throw new StreamNotSetException();

            if (!IsConnected)
                throw new DisconnectionException();

            Packet result;

            try
            {
                result = await Converter.GetPacketAsync();
            }
            catch
            {
                Disconnect();
                throw;
            }


            return result;
        }

        private IEnumerable<byte> RequestDataCallback()
        {
            byte[] buffer = new byte[BufferSize];
            int bytesRead;
            DataRequestCollection result;

            bytesRead = DataStream.Read(buffer, 0, BufferSize);

            result = new DataRequestCollection(buffer, 0, bytesRead);

            return result;
        }

        private async Task<IEnumerable<byte>> RequestDataCallbackAsync()
        {
            byte[] buffer = new byte[BufferSize];
            int bytesRead;
            DataRequestCollection result;

            bytesRead = await DataStream.ReadAsync(buffer, 0, BufferSize);

            result = new DataRequestCollection(buffer, 0, bytesRead);

            return result;
        }

        public abstract void Connect();

        public abstract void Disconnect();

        public abstract bool IsConnected
        {
            get;
        }
    }
}
